module;
#include <deque>
#include <cassert>
#include <memory>
#include <vector>
#include <iostream>
export module Code_5_7_1;
          
template<typename T, 
         template<typename Elem, 
                  typename = std::allocator<Elem>> class Cont = std::deque>
class Stack 
{
private:
    Cont<T> elems; // elements
public:
    void push(T const&); // push element
    void pop(); // pop element
    T const& top() const; // return top element
    bool empty() const { // return whether the stack is emptyreturn elems.empty();
        return false;
    }

    // assign stack of elements of type T2
    template<typename T2, template<typename Elem2, typename = std::allocator<Elem2> >class Cont2>
    Stack<T, Cont>& operator= (Stack<T2, Cont2> const&);

    // to get access to private members of any Stack with elementsof type T2:
    template<typename, template<typename, typename>class>
    friend class Stack;
};

template<typename T, template<typename, typename> class Cont>
void Stack<T, Cont>::push(T const& elem)
{
    elems.push_back(elem); // append copy of passed elem
}

template<typename T, template<typename, typename> class Cont>
void Stack<T, Cont>::pop()
{
    assert(!elems.empty());
    elems.pop_back(); // remove last element
}

template<typename T, template<typename, typename> class Cont>
T const& Stack<T, Cont>::top() const
{
    assert(!elems.empty());
    return elems.back(); // return copy of last element
}

template<typename T, template<typename, typename> class Cont>
template<typename T2, template<typename, typename> class Cont2>
Stack<T, Cont>&
Stack<T, Cont>::operator= (Stack<T2, Cont2> const& op2)
{
    elems.clear(); // remove existing elements
    elems.insert(
                 elems.begin(), // insert at the beginning
                 op2.elems.begin(), // all elements from op2
                 op2.elems.end()
    );
    return *this;
}

export
namespace Code_5_7_1 {

    void run()
    {
        Stack<int> iStack; // stack of ints
        Stack<float> fStack; // stack of floats
        // manipulate int stack
        iStack.push(1);
        iStack.push(2);
        std::cout << "iStack.top(): " << iStack.top() << '\n';
        // manipulate float stack:
        fStack.push(3.3);
        std::cout << "fStack.top(): " << fStack.top() << '\n';
        // assign stack of different type and manipulate again
        fStack = iStack;
        fStack.push(4.4);
        std::cout << "fStack.top(): " << fStack.top() << '\n';
        // stack for doubless using a vector as an internal container
        Stack<double, std::vector> vStack;
        vStack.push(5.5);
        vStack.push(6.6);
        std::cout << "vStack.top(): " << vStack.top() << '\n';
        vStack = fStack;
        std::cout << "vStack: ";
        while (!vStack.empty()) {
            std::cout << vStack.top() << ' ';
            vStack.pop();
        }
        std::cout << '\n';
    }
}
